﻿namespace MvvmTools
{
    using System;
    using System.ComponentModel;
    using System.Linq.Expressions;
    using JetBrains.Annotations;

    /// <summary>
    /// Provides infrastructure to listen to property changes, property value changes, etc.
    /// </summary>
    /// <typeparam name="T">
    /// The type of object to listen to.
    /// </typeparam>
    public interface IChangeListener<T> : IPropertyObservable 
        where T : INotifyPropertyChanged
    {
        /// <summary>
        /// Subscribes to property changes.
        /// </summary>
        /// <param name="propertySelector">
        /// The property selector.
        /// </param>
        /// <typeparam name="TProperty">
        /// The property.
        /// </typeparam>
        /// <returns>
        /// Subscription to property changes.
        /// </returns>
        /// <example>
        /// This sample shows how to use <see cref="OnChange{TProperty}"/> method.
        /// HandlePropertyChanges method called every time when:
        /// - property Class2 of MyClass1 is changed;
        /// - property Class3 of MyClass2 is changed;
        /// - value of the property SomeFlag of MyClass3 is changed.
        /// <code lang="cs">
        /// class MyClass1 : INotifyPropertyChanged
        /// {
        ///     public MyClass2 Class2 { get; set; } 
        /// }
        /// 
        /// class MyClass2 : INotifyPropertyChanged
        /// {
        ///     public MyClass3 Class3 { get; set; }
        /// }
        /// 
        /// class MyClass3 : INotifyPropertyChanged
        /// {
        ///     public bool SomeFlag { get; set; }
        /// }
        /// 
        /// class Example
        /// {
        ///     public void Test()
        ///     {
        ///         var target = new MyClass1();
        ///         target.Class2 = new MyClass2();
        ///         target.Class2.Class3 = new MyClass3();
        ///         target.Observe().OnChange(class1 => class1.Class2)
        ///                         .OnChange(class2 => class2.Class3)
        ///                         .OnValueChange(class3 => class3.SomeFlag)
        ///                         .Subscribe(x => HandlePropertyChanges());
        ///     }
        /// 
        ///     private void HandlePropertyChanges()
        ///     {
        ///         // Handle SomeFlag changes
        ///     }
        /// }
        /// </code>
        /// </example>
        [NotNull] IChangeListener<TProperty> OnChange<TProperty>([NotNull] Expression<Func<T, TProperty>> propertySelector)
            where TProperty : INotifyPropertyChanged;

        /// <summary>
        /// Subscribes to value changed event.
        /// </summary>
        /// <param name="propertySelector">
        /// The property selector.
        /// </param>
        /// <typeparam name="TProperty">
        /// The property.
        /// </typeparam>
        /// <returns>
        /// Subscription to value changes.
        /// </returns>
        /// <example>
        /// This sample shows how to use <see cref="OnValueChange{TProperty}"/> method.
        /// <code lang="cs">
        /// class MyClass : INotifyPropertyChanged
        /// {
        ///     public bool SomeFlag { get; set; }
        /// }
        /// 
        /// class Example
        /// {
        ///     public void Test()
        ///     {
        ///         var target = new MyClass();
        ///         target.Observe().OnValueChange(myClass => myClass.SomeFlag).Subscribe(x => HandlePropertyChanges());
        ///     }
        /// 
        ///     private void HandlePropertyChanges()
        ///     {
        ///         // Handle SomeFlag  changes
        ///     }
        /// }
        /// </code>
        /// </example>
        [NotNull] IPropertyObservable OnValueChange<TProperty>([NotNull] Expression<Func<T, TProperty>> propertySelector);

        /// <summary>
        /// Subscribes to either of two events, namely, an observable object that is associated 
        /// with the specified property was changed or it provides new data.
        /// </summary>
        /// <typeparam name="TElement">
        /// The type of an element within <see><cref>IObservable{TElement}</cref></see> source.
        /// </typeparam>
        /// <param name="propertySelector">
        /// A lambda expression which returns the reference to a property that has to be observed.
        /// </param>
        /// <returns>
        /// The <see cref="IPropertyObservable"/> subscription.
        /// </returns>
        /// <example>
        /// This sample shows how to use <see cref="OnChange{TProperty}"/> method.
        /// HandlePropertyChanges method called every time when:
        /// - the IntegerValues property of an instance of the MyClass has been changed;
        /// - the IntegerValues property, that is an observable source, has produced new data.
        /// <code lang="cs">
        /// class MyClass : INotifyPropertyChanged
        /// {
        ///     public IObservable&lt;int&gt; IntegerValues { get; set; }
        /// }
        /// 
        /// class Example
        /// {
        ///     public void Test()
        ///     {
        ///         var i = 0;
        ///         var obj = new MyClass();
        /// 
        ///         obj.Observe().OnObservableChange(o => o.IntegerValues).Subscribe(_ => Console.WriteLine(i++)); 
        ///         obj.IntegerValue = Observable.Range(0, 9);
        ///         // Here the "i" variable would be equal to 10.
        ///     }
        /// }
        /// </code>
        /// </example>
        [NotNull] IPropertyObservable OnObservableChange<TElement>([NotNull] Expression<Func<T, IObservable<TElement>>> propertySelector);
        
        /// <summary>
        /// Subscribes to value changed event for all changes.
        /// </summary>
        /// <returns>
        /// Subscription to value changes.
        /// </returns>
        /// <example>
        /// Subscribing to all properties changes.
        /// <code>
        /// class MyClass : INotifyPropertyChanged
        /// {
        ///     public bool SomeFlag { get; set; }
        /// 
        ///     public string SomeString { get; set; }
        /// }
        /// 
        /// class Example
        /// {
        ///     public void Test()
        ///     {
        ///         var target = new MyClass();
        ///         target.Observe().OnAnyChanges().Subscribe(x => HandlePropertyChanges());
        ///     }
        /// 
        ///     private void HandlePropertyChanges()
        ///     {
        ///         // Handle SomeFlag and SomeString changes
        ///     }
        /// }
        /// </code>
        /// </example>
        [NotNull] IPropertyObservable OnAnyChanges();
    }
}